﻿//Author: Jason Lattimer - based on sample from Jim Daly - Microsoft 

//Requires form libraries:
//    lat_/CRM2013DuplicateDetection/scripts/SDK.Metadata.Query.min.js
//    lat_/CRM2013DuplicateDetection/scripts/Xrm.DupDetect.js

//Event Handler:
//  Form - OnLoad
//    Xrm.DupDetect.config [lat_/CRM2013DuplicateDetection/scripts/Xrm.DupDetect.js]
//  Disable - Pass execution context as first parameter
//  Comma separated list of parameters...
//    [ {"preventsaveduplicate": false} ]

Xrm = window.Xrm || { __namespace: true };
Xrm.DupDetect = Xrm.DupDetect || { __namespace: true };
Xrm.DupDetect.saveMode = null;
Xrm.DupDetect.allowSave = false;
Xrm.DupDetect.alwaysAllowSave = false;
Xrm.DupDetect.neverAllowSave = false;
Xrm.DupDetect.cancel = false;
Xrm.DupDetect.action = "create";
Xrm.DupDetect.attributesNames = [];
Xrm.DupDetect.attributesMetadata = [];
Xrm.DupDetect.duplicateRecords = [];
Xrm.DupDetect.primaryIdAttribute = null;
Xrm.DupDetect.primaryNameAttribute = null;
Xrm.DupDetect.schemaName = null;
Xrm.DupDetect.onSaveAdded = false;
Xrm.DupDetect.viewAttributes = [];
Xrm.DupDetect.viewCurrentValues = [];
Xrm.DupDetect.duplicateDetectionRulesAndFields = [];
Xrm.DupDetect.retrieveDuplicateRequestCount = 0;
Xrm.DupDetect.exObj = null;

Xrm.DupDetect.config = function (config) {
    if (Xrm.Page.context.client.getClient() === "Mobile") return; //Not enabled for mobile clients

    //Read the optional configuration
    if (config !== undefined) {
        if (config instanceof Array) {
            //First option is to prevent users from saving if there are duplicates - something missing from the OOB 2011 implementation
            if (typeof config[0].preventsaveduplicate !== "boolean") {
                throw new Error('Xrm.DupDetect.config config parameter array value 0 must be {"preventsaveduplicate": true/false}');
            }
            if (config[0].preventsaveduplicate === true) {
                Xrm.DupDetect.neverAllowSave = true;
            }
        }
    }

    var formType = Xrm.Page.ui.getFormType();
    switch (formType) {
        case 1:
            Xrm.DupDetect.action = "create";
            break;
        case 2:
            Xrm.DupDetect.action = "update";
            break;
        case 0:
        case 3:
        case 4:
        case 5:
        case 6:
        case 11:
            Xrm.DupDetect.action = "none";
            break;
    }

    if (Xrm.DupDetect.action === "create" || Xrm.DupDetect.action === "update") {
        //Check for published duplicate detection rules on this entity - if none exist there is nothing more to do
        Xrm.DupDetect.getDuplicateDetectionRulesAndFields();
        if (Xrm.DupDetect.alwaysAllowSave) return;

        var mdq = SDK.Metadata.Query;
        var semp = mdq.SearchableEntityMetadataProperties;
        var samp = mdq.SearchableAttributeMetadataProperties;
        var emp = mdq.EntityMetadataProperties;
        var amp = mdq.AttributeMetadataProperties;

        Xrm.DupDetect.attributesMetadata = [];

        //We need these fields on the display window - add them to the list of fields
        for (var rule in Xrm.DupDetect.duplicateDetectionRulesAndFields) {
            Xrm.DupDetect.duplicateDetectionRulesAndFields[rule].Fields.push("statecode");
            Xrm.DupDetect.duplicateDetectionRulesAndFields[rule].Fields.push("modifiedon");
        }

        //Get Metadata for the fields used in the duplicate detection rules for the base & matching entities
        for (var entity in Xrm.DupDetect.duplicateDetectionRulesAndFields) {
            var entityFilter = new mdq.MetadataFilterExpression(mdq.LogicalOperator.And);
            entityFilter.addCondition(semp.LogicalName, mdq.MetadataConditionOperator.Equals, entity);
            var entityProperties = new mdq.MetadataPropertiesExpression(false, [emp.Attributes, emp.PrimaryIdAttribute, emp.PrimaryNameAttribute, emp.SchemaName, emp.LogicalName]);
            var attributesFilter = new mdq.MetadataFilterExpression(mdq.LogicalOperator.Or);
            attributesFilter.addCondition(samp.LogicalName, mdq.MetadataConditionOperator.In, Xrm.DupDetect.duplicateDetectionRulesAndFields[entity].Fields);
            attributesFilter.addCondition(samp.IsPrimaryName, mdq.MetadataConditionOperator.Equals, true);
            var attributeProperties = new mdq.MetadataPropertiesExpression(false, [amp.DisplayName, amp.AttributeType, amp.OptionSet, amp.IsPrimaryName, amp.SchemaName]);
            var labelQuery = new mdq.LabelQueryExpression([Xrm.Page.context.getUserLcid()]);
            var query = new mdq.EntityQueryExpression(entityFilter, entityProperties, new mdq.AttributeQueryExpression(attributesFilter, attributeProperties), null, labelQuery);
            var request = new mdq.RetrieveMetadataChangesRequest(query);
            mdq.RetrieveMetadataChanges(request, Xrm.DupDetect.recieveMetadata, function (error) { Xrm.DupDetect.writeToConsole(error.message); });
        }
    }
};

Xrm.DupDetect.recieveMetadata = function (retrieveMetadataChangesResponse) {
    //Receive and store the Metadata we need
    if (Xrm.Page.data.entity.getEntityName() === retrieveMetadataChangesResponse.EntityMetadata[0].LogicalName) {
        Xrm.DupDetect.schemaName = retrieveMetadataChangesResponse.EntityMetadata[0].SchemaName;
    }

    var attributes = retrieveMetadataChangesResponse.EntityMetadata[0].Attributes;
    for (var i = 0; i < attributes.length; i++) {
        var attribute = attributes[i];
        var label = "[No Label]";
        if ((attribute.DisplayName.UserLocalizedLabel !== null) && (attribute.DisplayName.UserLocalizedLabel.Label !== null)) {
            label = attribute.DisplayName.UserLocalizedLabel.Label;
        }
        var att = {};
        att.parentEntitySchemaName = retrieveMetadataChangesResponse.EntityMetadata[0].SchemaName;
        att.name = attribute.LogicalName;
        att.type = attribute.AttributeType;
        att.label = label;
        att.schemaName = attribute.SchemaName;
        att.IsPrimaryName = attribute.IsPrimaryName;
        if (attribute.AttributeType === "Picklist" || attribute.AttributeType === "State") {
            att.options = attribute.OptionSet.Options;
        }
        Xrm.DupDetect.attributesMetadata.push(att);
    }

    //Wire up the OnSave event to invoke the duplicate detection
    if (!Xrm.DupDetect.onSaveAdded) {
        Xrm.Page.data.entity.addOnSave(Xrm.DupDetect.checkForDuplicatesRequest);
        Xrm.DupDetect.onSaveAdded = true;
    }
};

Xrm.DupDetect.checkForDuplicatesRequest = function (executionObj) {
    var saveModeCode = executionObj.getEventArgs().getSaveMode();
    //If previsuly cancelled- do not re-show the display window
    if (Xrm.DupDetect.cancel) {
        Xrm.DupDetect.cancel = false;
        executionObj.getEventArgs().preventDefault();
        if (saveModeCode !== 1) {
            return;
        }
    }

    if (Xrm.DupDetect.alwaysAllowSave) return; //No rules published or specified from the display window

    if ((executionObj.getEventArgs().getSaveMode() === 70 || executionObj.getEventArgs().getSaveMode() === 1)
        && !Xrm.Page.data.entity.getIsDirty()) return; //Autosave or save with no changes

    if (Xrm.DupDetect.allowSave) { //No duplicates detected
        Xrm.DupDetect.allowSave = false;
        return;
    }

    Xrm.DupDetect.exObj = executionObj;

    switch (saveModeCode) {
        case 1:
            Xrm.DupDetect.saveMode = "save";
            break;
        case 2:
            Xrm.DupDetect.saveMode = "saveandclose";
            break;
        case 59:
            Xrm.DupDetect.saveMode = "saveandnew";
            break;
        case 70:
            Xrm.DupDetect.saveMode = null;
            break;
    }

    var id = (Xrm.Page.data.entity.getId() === "") ? "00000000-0000-0000-0000-000000000000" : Xrm.Page.data.entity.getId();

    //Reset the duplicate records and determine how many requests over the matching entities need to be made
    Xrm.DupDetect.duplicateRecords = [];
    Xrm.DupDetect.retrieveDuplicateRequestCount = 0;
    for (var rule in Xrm.DupDetect.duplicateDetectionRulesAndFields) {
        Xrm.DupDetect.retrieveDuplicateRequestCount++;
    }

    //Check for duplicate records against each matching entity type
    for (var entity in Xrm.DupDetect.duplicateDetectionRulesAndFields) {
        var autoSaveFieldChange = false;
        var attributesXml = [];
        var attributes = Xrm.DupDetect.attributesMetadata;
        for (var i = 0; i < attributes.length; i++) {
            if (attributes[i].parentEntitySchemaName !== Xrm.DupDetect.schemaName) continue; //Only process current entity type fields
            if (Xrm.Page.getAttribute(attributes[i].name) === null) continue; //Only process fields on the form       
            if (Xrm.Page.getAttribute(attributes[i].name).getSubmitMode() === "never") continue; //Exclude read-only (not-overridden) fields on updates
            if (Xrm.DupDetect.saveMode === null) { //Verify a field used in duplicate detection changed 
                if (Xrm.Page.getAttribute(attributes[i].name).getIsDirty()) {
                    autoSaveFieldChange = true;
                }
            }

            //Create the KeyValuePairs for the SOAP request
            attributesXml.push("<a:KeyValuePairOfstringanyType>");
            attributesXml.push("<c:key>" + attributes[i].name + "</c:key>");
            var value = Xrm.Page.getAttribute(attributes[i].name).getValue();
            if (value === null) {
                attributesXml.push("<c:value i:nil=\"true\" />");
            } else {
                switch (attributes[i].type) {
                    case "Customer":
                    case "Owner":
                    case "Lookup":
                        attributesXml.push("<c:value i:type='a:EntityReference'>");
                        attributesXml.push("<a:Id>" + value[0].id + "</a:Id>");
                        attributesXml.push("<a:LogicalName>" + value[0].entityType + "</a:LogicalName>");
                        attributesXml.push("<a:Name>" + CrmEncodeDecode.CrmXmlEncode(value[0].name) + "</a:Name></c:value>");
                        break;
                    case "Picklist":
                        attributesXml.push("<c:value i:type='a:OptionSetValue'>");
                        attributesXml.push("<a:Value>" + value + "</a:Value>");
                        attributesXml.push("</c:value>");
                        break;
                    case "DateTime":
                        attributesXml.push("<c:value i:type=\"d:dateTime\" xmlns:d=\"http://www.w3.org/2001/XMLSchema\">");
                        attributesXml.push(new Date(value).toISOString());
                        attributesXml.push("</c:value>");
                        break;
                    case "Integer":
                        attributesXml.push("<c:value i:type=\"d:int\" xmlns:d=\"http://www.w3.org/2001/XMLSchema\">");
                        attributesXml.push(value);
                        attributesXml.push("</c:value>");
                        break;
                    default:
                        attributesXml.push("<c:value i:type=\"d:" + attributes[i].type.toLowerCase() + "\"");
                        attributesXml.push(" xmlns:d=\"http://www.w3.org/2001/XMLSchema\">" + CrmEncodeDecode.CrmXmlEncode(value) + "</c:value>");
                }
            }
            attributesXml.push("</a:KeyValuePairOfstringanyType>");
        }

        if (Xrm.DupDetect.saveMode === null && autoSaveFieldChange === false) return; //If autosave and no fields used in duplicate detection changed - exit

        //Create the full SOAP request
        var requestXml = [
            "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">",
            "<s:Body>",
            "<Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\"",
            " xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">",
            "<request i:type=\"b:RetrieveDuplicatesRequest\"",
            " xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\"",
            " xmlns:b=\"http://schemas.microsoft.com/crm/2011/Contracts\">",
            "<a:Parameters xmlns:c=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">",
            "<a:KeyValuePairOfstringanyType>",
            "<c:key>BusinessEntity</c:key>",
            "<c:value i:type=\"a:Entity\">",
            "<a:Attributes>",
            attributesXml.join(""),
            "</a:Attributes>",
            "<a:EntityState i:nil=\"true\" />",
            "<a:FormattedValues />",
            "<a:Id>" + id + "</a:Id>",
            "<a:LogicalName>" + Xrm.Page.data.entity.getEntityName() + "</a:LogicalName>",
            "<a:RelatedEntities />",
            "</c:value>",
            "</a:KeyValuePairOfstringanyType>",
            "<a:KeyValuePairOfstringanyType>",
            "<c:key>MatchingEntityName</c:key>",
            "<c:value i:type=\"d:string\"",
            " xmlns:d=\"http://www.w3.org/2001/XMLSchema\">" + entity + "</c:value>",
            "</a:KeyValuePairOfstringanyType>",
            "<a:KeyValuePairOfstringanyType>",
            "<c:key>PagingInfo</c:key>",
            "<c:value i:type=\"a:PagingInfo\">",
            "<a:Count>50</a:Count>", //Limiting to first 50 records with no paging support
            "<a:PageNumber>1</a:PageNumber>", //Limiting to first 50 records with no paging support
            "<a:PagingCookie i:nil=\"true\" />",
            "<a:ReturnTotalRecordCount>false</a:ReturnTotalRecordCount>",
            "</c:value>",
            "</a:KeyValuePairOfstringanyType>",
            "</a:Parameters>",
            "<a:RequestId i:nil=\"true\" />",
            "<a:RequestName>RetrieveDuplicates</a:RequestName>",
            "</request>",
            "</Execute>",
            "</s:Body>",
            "</s:Envelope>"
        ];

        var req = new XMLHttpRequest();
        req.open("POST", Xrm.Page.context.getClientUrl() + "/XRMServices/2011/Organization.svc/web", false);

        try {
            req.responseType = 'msxml-document';
        } catch (e) { }
        req.setRequestHeader("Accept", "application/xml, text/xml, */*");
        req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
        req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
        req.onreadystatechange = function () { Xrm.DupDetect.checkForDuplicatesResponse(req); };
        req.send(requestXml.join(""));
    }
};

//Receive the SOAP response
Xrm.DupDetect.checkForDuplicatesResponse = function (req) {
    if (req.readyState === 4) {
        if (req.status === 200) {
            var doc = req.responseXML;
            try {
                Xrm.DupDetect._setSelectionNamespaces(doc);
            } catch (e) { }
            var duplicates = Xrm.DupDetect._selectNodes(doc, "//c:value[@i:type='a:EntityCollection']/a:Entities/a:Entity");
            Xrm.DupDetect.objectifyResults(duplicates);
        } else {
            Xrm.DupDetect.writeToConsole(Xrm.DupDetect.getError(req.responseXML).message);
        }
    }
};

Xrm.DupDetect.getError = function (faultXml) {
    ///<summary>
    /// Parses the WCF fault returned in the event of an error.
    ///</summary>
    ///<param name="faultXml" Type="XML">
    /// The responseXML property of the XMLHttpRequest response.
    ///</param>
    var errorMessage = "Unknown Error (Unable to parse the fault)";
    if (typeof faultXml === "object") {
        try {
            var bodyNode = faultXml.firstChild.firstChild;
            //Retrieve the fault node
            for (var i = 0; i < bodyNode.childNodes.length; i++) {
                var node = bodyNode.childNodes[i];

                //NOTE: This comparison does not handle the case where the XML namespace changes
                if ("s:Fault" === node.nodeName) {
                    for (var j = 0; j < node.childNodes.length; j++) {
                        var faultStringNode = node.childNodes[j];
                        if ("faultstring" === faultStringNode.nodeName) {
                            errorMessage = faultStringNode.text;
                            break;
                        }
                    }
                    break;
                }
            }
        } catch (e) { }
    }
    return new Error(errorMessage);
};

Xrm.DupDetect.objectifyResults = function (duplicates) {
    Xrm.DupDetect.retrieveDuplicateRequestCount--;

    if (duplicates.length > 0) {
        for (var i = 0; i < duplicates.length; i++) {
            Xrm.DupDetect.duplicateRecords.push(Xrm.DupDetect.objectifyRecord(duplicates[i].cloneNode(true)));
        }
    }

    //If there are no more duplicate detection requests and 0 duplicates found - exit
    if (Xrm.DupDetect.duplicateRecords.length === 0 && Xrm.DupDetect.retrieveDuplicateRequestCount === 0) return;
    //If there are more pending duplicate detection requests - exit
    if (Xrm.DupDetect.retrieveDuplicateRequestCount !== 0) return; 

    Xrm.DupDetect.getCurrentValues();

    Xrm.DupDetect.writeToConsole("Duplicate records identified");
    //Open the display window with the results
    var dialogResults = showModalDialog(Xrm.Page.context.getClientUrl() + "/webresources/lat_/CRM2013DuplicateDetection/Xrm.ShowDuplicates.html",
        {
            action: Xrm.DupDetect.action, records: Xrm.DupDetect.duplicateRecords, current: Xrm.DupDetect.viewCurrentValues, neverAllowSave: Xrm.DupDetect.neverAllowSave,
            saveModeCode: Xrm.DupDetect.saveMode, url: Xrm.Page.context.getClientUrl()
        },
        "dialogHeight: 600px; dialogWidth: 800px; center: Yes; resizable: Yes; status: No;");
    if (dialogResults === undefined) return; //Same as cancel
    switch (dialogResults[0]) {
        case "save":
            Xrm.DupDetect.allowSave = true;
            break;
        case "cancel":
            Xrm.DupDetect.cancel = true;
            Xrm.DupDetect.exObj.getEventArgs().preventDefault();
            break;
        case "nevershow":
            Xrm.DupDetect.alwaysAllowSave = true;
            break;
    }
};

//Process the resulting XML for use in the display window
Xrm.DupDetect.objectifyRecord = function (node) {
    var record = {};
    record.attributes = [];
    record.logicalName = (node.childNodes[4].text !== undefined) ? node.childNodes[4].text : node.childNodes[4].textContent;
    record.id = (node.childNodes[3].text !== undefined) ? node.childNodes[3].text : node.childNodes[3].textContent;

    var c = 3;
    for (var i = 0; i < Xrm.DupDetect.attributesMetadata.length; i++) {
        if (Xrm.DupDetect.attributesMetadata[i].parentEntitySchemaName.toLowerCase() !== record.logicalName) continue;
        var attribute = Xrm.DupDetect.attributesMetadata[i];
        var att = {};
        att.AttributeName = attribute.name;
        att.SchemaName = attribute.schemaName;
        att.AttributeType = attribute.type.toLowerCase();
        att.Options = attribute.options;
        att.Label = attribute.label;
        att.IsPrimaryName = attribute.IsPrimaryName;
        att.DisplayPosition = Xrm.DupDetect._setDisplayPosition(att, c);
        if (att.DisplayPosition >= c) {
            c = att.DisplayPosition;
        }

        switch (att.AttributeType) {
            case "money":
                att.Value = Xrm.DupDetect._selectSingleNodeText(node, "//a:Attributes/a:KeyValuePairOfstringanyType[c:key = '" + attribute.name + "']/c:value/a:Value");
                break;
            case "state":
            case "picklist":
                att.Value = Xrm.DupDetect._selectSingleNodeText(node, "//a:Attributes/a:KeyValuePairOfstringanyType[c:key = '" + attribute.name + "']/c:value/a:Value");
                if (att.Value !== null) {
                    att.Value = Xrm.DupDetect._getOptionsetLabel(attribute.options, att.Value);
                }
                break;
            case "customer":
            case "owner":
            case "lookup":
                att.Value = {};
                att.Value.Id = Xrm.DupDetect._selectSingleNodeText(node, "//a:Attributes/a:KeyValuePairOfstringanyType[c:key = '" + attribute.name + "']/c:value/a:Id");
                att.Value.LogicalName = Xrm.DupDetect._selectSingleNodeText(node, "//a:Attributes/a:KeyValuePairOfstringanyType[c:key = '" + attribute.name + "']/c:value/a:LogicalName");
                att.Value.Name = Xrm.DupDetect._selectSingleNodeText(node, "//a:Attributes/a:KeyValuePairOfstringanyType[c:key = '" + attribute.name + "']/c:value/a:Name");
                break;
            default:
                att.Value = Xrm.DupDetect._selectSingleNodeText(node, "//a:Attributes/a:KeyValuePairOfstringanyType[c:key = '" + attribute.name + "']/c:value");
                break;
        }
        record.attributes.push(att);
    }
    record.attributes.sort(function (a, b) {
        return a.DisplayPosition - b.DisplayPosition;
    });

    return record;
};

//Get values of the current record to be shown on the display window
Xrm.DupDetect.getCurrentValues = function () {
    var record = {};
    record.id = Xrm.Page.data.entity.getId();
    record.logicalName = Xrm.Page.data.entity.getEntityName();
    record.attributes = [];

    var c = 3;
    for (var i = 0; i < Xrm.DupDetect.attributesMetadata.length; i++) {
        if (Xrm.DupDetect.attributesMetadata[i].parentEntitySchemaName.toLowerCase() !== record.logicalName) continue;
        var attribute = Xrm.DupDetect.attributesMetadata[i];
        var att = {};
        att.AttributeName = attribute.name;
        att.SchemaName = attribute.schemaName;
        att.AttributeType = attribute.type.toLowerCase();
        att.Options = attribute.options;
        att.Label = attribute.label;
        att.IsPrimaryName = attribute.IsPrimaryName;
        att.DisplayPosition = Xrm.DupDetect._setDisplayPosition(att, c);
        att.Value = null;
        if (att.DisplayPosition >= c) {
            c = att.DisplayPosition;
        }
        record.attributes.push(att);
    }

    record.attributes.sort(function (a, b) {
        return a.DisplayPosition - b.DisplayPosition;
    });

    for (var b = 0; b < record.attributes.length; b++) {
        if (record.attributes[b].AttributeName === "modifiedon" || record.attributes[b].AttributeName === "statecode") {
            record.attributes.splice(b, 1);
            b--;
        }
    }

    var selectFields = [];
    for (var i = 0; i < record.attributes.length; i++) {
        var field = Xrm.Page.getAttribute(record.attributes[i].AttributeName); //Check if field is on form
        if (field === null) {
            selectFields.push(record.attributes[i].SchemaName); //Add to query to retrieve the current, un-displayed value
        } else { //Get the current value
            if (record.attributes[i].AttributeType === "owner" || record.attributes[i].AttributeType === "lookup" || record.attributes[i].AttributeType === "customer") {
                record.attributes[i].Value = {};
                if (field.getValue() !== null) {
                    record.attributes[i].Value = {};
                    record.attributes[i].Value.Id = field.getValue()[0].id;
                    record.attributes[i].Value.LogicalName = field.getValue()[0].entityType;
                    record.attributes[i].Value.Name = field.getValue()[0].name;
                }
            }
            else if (record.attributes[i].AttributeType === "picklist" || record.attributes[i].AttributeType === "state") {
                record.attributes[i].Value = field.getValue();
                if (record.attributes[i].Value !== null) {
                    record.attributes[i].Value = Xrm.DupDetect._getOptionsetLabel(record.attributes[i].Options, record.attributes[i].Value);
                }
            } else {
                record.attributes[i].Value = field.getValue();
            }
        }
    }
    Xrm.DupDetect.viewCurrentValues = record;

    //Retrieve any un-displayed field values to be shown on the display window
    if (selectFields.length > 0 && Xrm.DupDetect.action !== "create") {
        var oDataSelect = Xrm.Page.context.getClientUrl() + "/XRMServices/2011/OrganizationData.svc/" +
            Xrm.DupDetect.schemaName + "Set(guid'" + Xrm.Page.data.entity.getId() + "') ?$select=" + selectFields.join(",");
        var req = new XMLHttpRequest();
        req.open("GET", oDataSelect, false);
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json;charset=utf-8");
        req.onreadystatechange = function () {
            if (req.readyState === 4) {
                if (req.status === 200) {
                    //Add the retrieved values 
                    var retrieved = JSON.parse(req.responseText).d;
                    for (var x = 0; x < selectFields.length; x++) {
                        var result = retrieved[selectFields[x]];
                        for (var y = 0; y < record.attributes.length; y++) {
                            if (record.attributes[y].SchemaName === selectFields[x]) {
                                if (record.attributes[y].AttributeType === "owner" || record.attributes[y].AttributeType === "lookup" || record.attributes[y].AttributeType === "customer") {
                                    record.attributes[i].Value = {};
                                    record.attributes[y].Value.Id = result[0].Id;
                                    record.attributes[y].Value.LogicalName = result[0].LogicalName;
                                    record.attributes[y].Value.Name = result[0].Name;
                                }
                                else if (record.attributes[y].AttributeTypee === "picklist" || record.attributes[y].AttributeType === "state") {
                                    record.attributes[i].Value = result;
                                    if (record.attributes[y].Value !== null) {
                                        record.attributes[y].Value = Xrm.DupDetect._getOptionsetLabel(record.attributes[y].Options, record.attributes[y].Value);
                                    }
                                } else {
                                    record.attributes[y].Value = result;
                                }
                            }
                        }
                    }
                } else {
                    Xrm.DupDetect.writeToConsole(Xrm.DupDetect.getError(req.responseXML).message);
                }
            }
        };
        req.send();
    }
};

//Get the list of matching entities and fields when matching against the current entity
Xrm.DupDetect.getDuplicateDetectionRulesAndFields = function () {
    var oDataSelect = Xrm.Page.context.getClientUrl() + "/XRMServices/2011/OrganizationData.svc/" +
        "DuplicateRuleSet?$select=MatchingEntityName," +
        "DuplicateRule_DuplicateRuleConditions/MatchingAttributeName&$filter=BaseEntityName eq '" + Xrm.Page.data.entity.getEntityName() + "' " +
        "and StatusCode/Value eq 2&$expand=DuplicateRule_DuplicateRuleConditions";
    var req = new XMLHttpRequest();
    req.open("GET", oDataSelect, false);
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json;charset=utf-8");
    req.onreadystatechange = function () {
        if (req.readyState === 4) {
            if (req.status === 200) {
                var retrieved = JSON.parse(req.responseText).d;
                if (retrieved.results !== null) {
                    for (var x = 0; x < retrieved.results.length; x++) {
                        if (Xrm.DupDetect.duplicateDetectionRulesAndFields[retrieved.results[x].MatchingEntityName] === undefined) {
                            Xrm.DupDetect.duplicateDetectionRulesAndFields[retrieved.results[x].MatchingEntityName] = {};
                        }
                        if (Xrm.DupDetect.duplicateDetectionRulesAndFields[retrieved.results[x].MatchingEntityName].Fields === undefined) {
                            Xrm.DupDetect.duplicateDetectionRulesAndFields[retrieved.results[x].MatchingEntityName].Fields = [];
                        }
                        for (var y = 0; y < retrieved.results[x].DuplicateRule_DuplicateRuleConditions.results.length; y++) {
                            Xrm.DupDetect.duplicateDetectionRulesAndFields[retrieved.results[x].MatchingEntityName].Fields.push(retrieved.results[x].DuplicateRule_DuplicateRuleConditions.results[y].MatchingAttributeName);
                        }
                    }
                    //Remove any duplicate fields from the list
                    for (var i = 0; i < Xrm.DupDetect.duplicateDetectionRulesAndFields.length; i++) {
                        Xrm.DupDetect.duplicateDetectionRulesAndFields[i].Fields = Xrm.DupDetect._removeArrayDuplicates(Xrm.DupDetect.duplicateDetectionRulesAndFields[i].Fields);
                    }

                } else {
                    Xrm.DupDetect.writeToConsole("No Published Duplicate Detection Rules For " + Xrm.Page.data.entity.getEntityName());
                    Xrm.DupDetect.alwaysAllowSave = true;
                }
            } else {
                Xrm.DupDetect.writeToConsole(Xrm.DupDetect.getError(req.responseXML).message);
            }
        }
    };
    req.send();
};

//Determines field order on display window
Xrm.DupDetect._setDisplayPosition = function (att, c) {
    if (att.IsPrimaryName) return 1;
    if (att.AttributeName === "modifiedon") return 2;
    if (att.AttributeName === "statecode") return 3;
    return ++c;
};

//Gets the text value of an option set
Xrm.DupDetect._getOptionsetLabel = function (options, value) {
    if (options === null || options === undefined) return "[No Label]";

    for (var i = 0; i < options.length; i++) {
        if (options[i].Value === parseInt(value)) {
            var option = options[i];
            if (option.Label === null) {
                return "[No Label]";
            }
            else {
                if (option.Label.UserLocalizedLabel !== null) {
                    return option.Label.UserLocalizedLabel.Label;
                } else {
                    for (var j = 0; j < option.Label.LocalizedLabels.length; j++) {
                        if (option.Label.LocalizedLabels[j].LanguageCode === Xrm.Page.context.getUserLcid()) {
                            return option.Label.LocalizedLabels[j].Label;
                        }
                    }
                }
            }
        }
    }
    return "[No Label]";
};

Xrm.DupDetect._selectNodes = function (node, xPathExpression) {
    if (typeof (node.selectNodes) != "undefined") {
        return node.selectNodes(xPathExpression);
    }
    else {
        var output = [];
        var xPathResults = node.evaluate(xPathExpression, node, Xrm.DupDetect._NSResolver, XPathResult.ANY_TYPE, null);
        var result = xPathResults.iterateNext();
        while (result) {
            output.push(result);
            result = xPathResults.iterateNext();
        }
        return output;
    }
};

Xrm.DupDetect._selectSingleNode = function (node, xpathExpr) {
    if (typeof (node.selectSingleNode) != "undefined") {
        return node.selectSingleNode(xpathExpr);
    }
    else {
        var xpe = new XPathEvaluator();
        var xPathNode = xpe.evaluate(xpathExpr, node, Xrm.DupDetect._NSResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
        return (xPathNode != null) ? xPathNode.singleNodeValue : null;
    }
};

Xrm.DupDetect._selectSingleNodeText = function (node, xpathExpr) {
    var x = Xrm.DupDetect._selectSingleNode(node, xpathExpr);
    if (Xrm.DupDetect._isNodeNull(x))
    { return null; }
    if (typeof (x.text) != "undefined") {
        return x.text;
    }
    else {
        return x.textContent;
    }
};

Xrm.DupDetect._isNodeNull = function (node) {
    if (node == null)
    { return true; }
    if ((node.attributes.getNamedItem("i:nil") != null) && (node.attributes.getNamedItem("i:nil").value == "true"))
    { return true; }
    return false;
};

Xrm.DupDetect._setSelectionNamespaces = function (doc) {
    var namespaces = [
        "xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'",
        "xmlns:a='http://schemas.microsoft.com/xrm/2011/Contracts'",
        "xmlns:i='http://www.w3.org/2001/XMLSchema-instance'",
        "xmlns:b='http://schemas.microsoft.com/crm/2011/Contracts'",
        "xmlns:c='http://schemas.datacontract.org/2004/07/System.Collections.Generic'"
    ];
    doc.setProperty("SelectionNamespaces", namespaces.join(" "));
};

Xrm.DupDetect._NSResolver = function (prefix) {
    var ns = {
        "s": "http://schemas.xmlsoap.org/soap/envelope/",
        "a": "http://schemas.microsoft.com/xrm/2011/Contracts",
        "i": "http://www.w3.org/2001/XMLSchema-instance",
        "b": "http://schemas.microsoft.com/crm/2011/Contracts",
        "c": "http://schemas.datacontract.org/2004/07/System.Collections.Generic"
    };
    return ns[prefix] || null;
};

Xrm.DupDetect.writeToConsole = function (message) {
    if (typeof console != "undefined") {
        console.log(message);
    }
};

Xrm.DupDetect._removeArrayDuplicates = function (input) {
    var i,
      len = input.length,
      out = [],
      obj = {};

    for (i = 0; i < len; i++) {
        obj[input[i]] = 0;
    }
    for (i in obj) {
        out.push(i);
    }
    return out;
};

Xrm.DupDetect._isInArray = function (value, inputArray) {
    var found = false;
    for (var i = 0, j = inputArray.length; !found && i < j; i++) {
        if (value.indexOf(inputArray[i]) > -1) {
            found = true;
        }
    }
    return found;
};

